#include "Session.h"

#include <QFile>
#include <QTimer>
#include <QImage>
#include <QBuffer>
#include <QFileInfo>
#include <QThreadPool>
#include <QJsonDocument>


static QString replyTextFormat(
	"HTTP/1.1 200 OK\r\n"
	"Content-Type: %1\r\n"
	"Content-Length: %2\r\n"
	"Access-Control-Allow-Origin: *\r\n"
	"\r\n"
	"%3"
	);

static QString replyFileFormat(
	"HTTP/1.1 200 OK\r\n"
	"Content-Disposition: attachment;filename=%1\r\n"
	"Content-Length: %2\r\n"
	"Access-Control-Allow-Origin: *\r\n"
	"\r\n"
	);

static QString replyImageFormat(
	"HTTP/1.1 200 OK\r\n"
	"Content-Type: image/png\r\n"
	"Content-Length: %1\r\n"
	"Access-Control-Allow-Origin: *\r\n"
	"\r\n"
	);

Session::Session(const QPointer<QIODevice> & tcpSocket, QObject * parent) : QObject(parent),
ioDevice_(tcpSocket),
timerForClose_(new QTimer)
{
	timerForClose_->setInterval(30 * 1000);

	connect(ioDevice_.data(), &QIODevice::readyRead, [this]()
	{
		if (this->timerForClose_->isActive())
		{
			timerForClose_->stop();
		}

		this->buffer_.append(this->ioDevice_->readAll());

		//        qDebug() << this->buffer_;

		this->inspectionBufferSetup1();

		timerForClose_->start();
	});

	connect(ioDevice_.data(), &QIODevice::bytesWritten, [this](const qint64 &bytes)
	{
		this->waitWrittenByteCount_ -= bytes;

		if (this->waitWrittenByteCount_ == 0)
		{
			this->deleteLater();
			return;
		}

		if (!ioDeviceForReply_.isNull())
		{
			if (ioDeviceForReply_->atEnd())
			{
				delete ioDeviceForReply_.data();
				ioDeviceForReply_.clear();
			}
			else
			{
				ioDevice_->write(ioDeviceForReply_->read(8 * 1024));
			}
		}

		if (this->timerForClose_->isActive())
		{
			timerForClose_->stop();
		}

		timerForClose_->start();
	});

	connect(timerForClose_.data(), &QTimer::timeout, this, &QObject::deleteLater);
}

Session::~Session()
{
	if (!ioDevice_.isNull())
	{
		delete ioDevice_.data();
	}
}

void Session::replyText(const QString &replyData)
{
	if (QThread::currentThread() != this->thread())
	{
		QMetaObject::invokeMethod(this, "replyText", Qt::QueuedConnection, Q_ARG(QString, replyData));
		return;
	}

	if (ioDevice_.isNull())
	{
		qDebug() << "JQHttpServer::Session::replyText: error1";
		this->deleteLater();
		return;
	}

	const auto &&data = replyTextFormat.arg("text;charset=UTF-8", QString::number(replyData.toUtf8().size()), replyData).toUtf8();

	waitWrittenByteCount_ = data.size();
	ioDevice_->write(data);
}

void Session::replyJsonObject(const QJsonObject &jsonObject)
{
	if (QThread::currentThread() != this->thread())
	{
		QMetaObject::invokeMethod(this, "replyJsonObject", Qt::QueuedConnection, Q_ARG(QJsonObject, jsonObject));
		return;
	}

	if (ioDevice_.isNull())
	{
		qDebug() << "JQHttpServer::Session::replyJsonObject: error1";
		this->deleteLater();
		return;
	}

	const auto &&data = QJsonDocument(jsonObject).toJson();
	const auto &&data2 = replyTextFormat.arg("application/json;charset=UTF-8", QString::number(data.size()), QString(data)).toUtf8();

	waitWrittenByteCount_ = data2.size();
	ioDevice_->write(data2);
}

void Session::replyJsonArray(const QJsonArray &jsonArray)
{
	if (QThread::currentThread() != this->thread())
	{
		QMetaObject::invokeMethod(this, "replyJsonArray", Qt::QueuedConnection, Q_ARG(QJsonArray, jsonArray));
		return;
	}

	if (ioDevice_.isNull())
	{
		qDebug() << "JQHttpServer::Session::replyJsonArray: error1";
		this->deleteLater();
		return;
	}

	const auto &&data = QJsonDocument(jsonArray).toJson();
	const auto &&data2 = replyTextFormat.arg("application/json;charset=UTF-8", QString::number(data.size()), QString(data)).toUtf8();

	waitWrittenByteCount_ = data2.size();
	ioDevice_->write(data2);
}

void Session::replyFile(const QString &filePath)
{
	if (QThread::currentThread() != this->thread())
	{
		QMetaObject::invokeMethod(this, "replyFile", Qt::QueuedConnection, Q_ARG(QString, filePath));
		return;
	}

	if (ioDevice_.isNull())
	{
		qDebug() << "JQHttpServer::Session::replyFile: error1";
		this->deleteLater();
		return;
	}

	ioDeviceForReply_.reset(new QFile(filePath));
	QPointer< QFile > file = (qobject_cast<QFile *>(ioDeviceForReply_.data()));

	if (!file->open(QIODevice::ReadOnly))
	{
		qDebug() << "JQHttpServer::Session::replyFile: open file error:" << filePath;
		ioDeviceForReply_.clear();
		this->deleteLater();
		return;
	}

	const auto &&data = replyFileFormat.arg(QFileInfo(filePath).fileName(), QString::number(file->size())).toUtf8();

	waitWrittenByteCount_ = data.size() + file->size();
	ioDevice_->write(data);
}

void Session::replyImage(const QImage &image)
{
	if (QThread::currentThread() != this->thread())
	{
		QMetaObject::invokeMethod(this, "replyImage", Qt::QueuedConnection, Q_ARG(QImage, image));
		return;
	}

	if (ioDevice_.isNull())
	{
		qDebug() << "JQHttpServer::Session::replyImage: error1";
		this->deleteLater();
		return;
	}

	auto buffer = new QBuffer;

	if (!buffer->open(QIODevice::ReadWrite))
	{
		qDebug() << "JQHttpServer::Session::replyImage: open buffer error";
		delete buffer;
		this->deleteLater();
		return;
	}

	if (!image.save(buffer, "PNG"))
	{
		qDebug() << "JQHttpServer::Session::replyImage: save image to buffer error";
		delete buffer;
		this->deleteLater();
		return;
	}

	ioDeviceForReply_.reset(buffer);
	ioDeviceForReply_->seek(0);

	const auto &&data = replyImageFormat.arg(QString::number(buffer->buffer().size())).toUtf8();

	waitWrittenByteCount_ = data.size() + buffer->buffer().size();
	ioDevice_->write(data);
}

void Session::inspectionBufferSetup1()
{
	if (!headerAcceptedFinish_)
	{
		forever
		{
		static QByteArray splitFlag("\r\n");

		auto splitFlagIndex = buffer_.indexOf(splitFlag);
		if (splitFlagIndex == -1) { return; }

		if (requestMethodToken_.isEmpty() && (splitFlagIndex == 0))
		{
			qDebug() << "JQHttpServer::Session::inspectionBuffer: error1";
			this->deleteLater();
			return;
		}

		if (requestMethodToken_.isEmpty())
		{
			auto requestLineDatas = buffer_.mid(0, splitFlagIndex).split(' ');
			buffer_.remove(0, splitFlagIndex + 2);

			if (requestLineDatas.size() != 3)
			{
				qDebug() << "JQHttpServer::Session::inspectionBuffer: error2";
				this->deleteLater();
				return;
			}

			requestMethodToken_ = requestLineDatas.at(0);
			requestUrl_ = requestLineDatas.at(1);
			requestCrlf_ = requestLineDatas.at(2);

			if ((requestMethodToken_ != "GET") &&
				(requestMethodToken_ != "POST"))
			{
				qDebug() << "JQHttpServer::Session::inspectionBuffer: error3:" << requestMethodToken_;
				this->deleteLater();
				return;
			}

			//                qDebug() << "requestMethodToken:" << requestMethodToken_;
			//                qDebug() << "requestUrl:" << requestUrl_;
			//                qDebug() << "requestCrlf:" << requestCrlf_;
		}
		else if (splitFlagIndex == 0)
		{
			buffer_.remove(0, 2);

			headerAcceptedFinish_ = true;

			if ((requestMethodToken_.toUpper() == "GET") ||
				((requestMethodToken_.toUpper() == "POST") && !buffer_.isEmpty()))
			{
				this->inspectionBufferSetup2();
			}
		}
		else
		{
			auto index = buffer_.indexOf(':');

			if (index <= 0)
			{
				qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
				this->deleteLater();
				return;
			}

			auto headerData = buffer_.mid(0, splitFlagIndex);
			buffer_.remove(0, splitFlagIndex + 2);

			const auto &&key = headerData.mid(0, index);
			auto value = headerData.mid(index + 1);

			if (value.startsWith(' '))
			{
				value.remove(0, 1);
			}

			headersData_[key] = value;

			//                qDebug() << "headerData:" << key << value;
		}
	}
	}
	else
	{
		this->inspectionBufferSetup2();
	}
}

void Session::inspectionBufferSetup2()
{
	requestRawData_ = buffer_;
	buffer_.clear();

	//    qDebug() << "requestRawData:" << requestRawData_;

	if (!handleAcceptedCallback_)
	{
		qDebug() << "JQHttpServer::Session::inspectionBuffer: error4";
		this->deleteLater();
		return;
	}
	handleAcceptedCallback_(this);
}
